//*************************************************************************//
//*           _____   ____________            ______               __     *//
//*          / /__ \ / ____/ ____/           / ____/___  _________/ /     *//
//*     __  / /__/ // __/ / __/    ______   / /_  / __ \/ ___/ __  /      *//
//*    / /_/ // __// /___/ /___   /_____/  / __/ / /_/ / /  / /_/ /       *//
//*    \____//____/_____/_____/           /_/    \____/_/   \__,_/        *//
//*                                                                       *//
//*     Copyright (c) 2006 Ford Argentina. All Rights Reserved.           *//
//*                                                                       *//
//*************************************************************************//

package ar.com.ford.it.webengine.struts;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.ford.it.logging.ILogger;
import com.ford.it.logging.Level;
import com.ford.it.logging.LogFactory;

/**
 * TokenProcessor <br>
 * Responsible for handling all token related functionality. The  methods in this 
 * class are synchronized to protect token processing from multiple threads.<br>
 * Use <b>TokenProcessor.getInstance()</b> to obtain a reference to the processor.
 * 
 * @author Aji
 */
public class TokenProcessor {

    private static final String CLASS_NAME = TokenProcessor.class.getName();
    
    private static final ILogger log = LogFactory.getInstance().getLogger(CLASS_NAME);

    /**
     * The singleton instance of this class
     */
    private static TokenProcessor instance = new TokenProcessor();

    /**
     * Retrieves the singleton instance of this class
     * @return the singleton instance of this class
     */
    public static TokenProcessor getInstance() {
        return instance;
    }

    /**
     * Protected constructor for TokenProcessor. 
     */
    protected TokenProcessor() {
        super();
    }
    
    private long previous;
    
    /**
     * Return <code>true</code> if there is a transaction token stored in
     * the user's current session for the specified action, and the value
     * submitted as a request parameter matches it. 
     * @param request The servlet request we are processing
     * @param action Path of the tokenized action
     */
    public synchronized boolean isTokenValid(HttpServletRequest request, String action){
        return this.isTokenValid(request, action, false);
    }

    /**
     * Return <code>true</code> if there is a transaction token stored in
     * the user's current session for the specified action, and the value
     * submitted as a request parameter matches it. 
     * @param request The servlet request we are processing
     * @param action Path of the tokenized action
     * @param resetToken Should we reset the token after checking it?
     */
    public synchronized boolean isTokenValid(HttpServletRequest request, String action, boolean resetToken){
        String METHOD_NAME = "isTokenValid";
        log.entering(CLASS_NAME, METHOD_NAME);
        
        HttpSession session = request.getSession(false);
        boolean result = false;

        if (session != null) {
             // Retrieve the transaction token from this session, and reset
             String paramName = action + ".TOKEN";
             String saved = (String) session.getAttribute(paramName);
             log.logp(Level.FINE, CLASS_NAME, METHOD_NAME, "Token from session for "+paramName, saved);
             if (saved != null) {
                 if (resetToken) {
                    session.removeAttribute(paramName);
                 }
         
                 // Retrieve the transaction token included in this request
                 String token = request.getParameter("requestTOKEN");
                 log.logp(Level.FINE, CLASS_NAME, METHOD_NAME, "Token from request",token);
                 if (token != null) {
                     result = saved.equals(token);
                 }
             }
        }
        
        log.exiting(CLASS_NAME, METHOD_NAME, Boolean.toString(result));
        return result;
    }

    /**
     * Save a transaction token in the user's current session for a specified
     * action.
     * @param request The servlet request we are processing
     * @param action Path of the tokenized action
     * @return saved token
     */
    public synchronized String saveToken(HttpServletRequest request, String action) {
        String METHOD_NAME = "saveToken";
        
        log.entering(CLASS_NAME, METHOD_NAME);
        
        HttpSession session = request.getSession(false);
        String token = null;
        if (session != null){
            String paramName = action + ".TOKEN";
            token = (String)session.getAttribute(paramName);
            if (token == null) {
            token = this.generateToken(session);
                if (token != null) {
                    log.logp(Level.FINE, CLASS_NAME, METHOD_NAME, "Token saved in "+paramName,token);
                    session.setAttribute(paramName, token);
                }
            }
        }
 
        log.exiting(CLASS_NAME, METHOD_NAME, token);
        return token;
    }
    
    /**
     * Remove transaction token in the user's current session for a specified action
     * @param request The servlet request we are processing
     * @param action Path of the tokenized action
     */
    public synchronized void resetToken(HttpServletRequest request, String action) {
        String METHOD_NAME = "resetToken";
        
        log.entering(CLASS_NAME, METHOD_NAME);
        
        HttpSession session = request.getSession(false);

        if (session != null) {
            String paramName = action + ".TOKEN";
            session.removeAttribute(paramName);
        }
 
        log.exiting(CLASS_NAME, METHOD_NAME);
    }
    

    /**
     * Generate a new transaction token.
     * @param session The user's current session
     * @return generated token
     */
    protected String generateToken(HttpSession session) {
        String METHOD_NAME = "generateToken";
        
        log.entering(CLASS_NAME, METHOD_NAME);
        
        String result;
 
         try {
             byte id[] = session.getId().getBytes();
             long current = System.currentTimeMillis();
             if (current == previous) {
                 current++;
             }
             previous = current;
             byte now[] = new Long(current).toString().getBytes();
             MessageDigest md = MessageDigest.getInstance("MD5");
             md.update(id);
             md.update(now);
             result = toHex(md.digest());

         } catch (NoSuchAlgorithmException e) {
             result = null;
         }
 
         log.exiting(CLASS_NAME, METHOD_NAME);
         return result;
     }
 
    /**
     * Convert a byte array to a String of hexadecimal digits and return it.
     * @param buffer The byte array to be converted
     */
     private String toHex(byte buffer[]) {
         String METHOD_NAME = "toHex";
         
         log.entering(CLASS_NAME, METHOD_NAME);
         
         StringBuffer sb = new StringBuffer(buffer.length * 2);
         for (int i = 0; i < buffer.length; i++) {
             sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
             sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
         }
         
         log.exiting(CLASS_NAME, METHOD_NAME);
         return sb.toString();
     }

}